<template>
  <div class="fx-autocomplete" ref="autocompleteDom">
    <fx-input v-model="currentValue" @focus="autocompleteFocus" @blur="autocompleteBlur"
              @keydown.stop="autocompleteKeydown" @input="autocompleteListShow=true" v-bind="$attrs"></fx-input>
    <div class="autocomplete-list" ref="autocompleteListDom" v-show="autocompleteListShow&&fetchList.length">
      <ul :class="{top:isTopDropdown}">
        <li v-for="(item,index) in fetchList" :key="item[searchKey]" :class="{active:index===currentIndex}"
            @click="autocompleteClick(item)" ref="itemDom">
          <slot :item="item">{{ item[searchKey] }}</slot>
        </li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, onBeforeUnmount, onMounted, PropType, ref, SetupContext } from 'vue'
import { getZIndex, useClickBlank, useModelWatch } from '@/base/hooks'

interface FetchList {
  value?: string;

  [key: string]: unknown
}

interface AutocompleteProps {
  modelValue: string;
  fetch: (val: string, cb: (val: FetchList[]) => void) => void;
  searchKey: string;
}

export default defineComponent({
  name: 'FAutocomplete',
  props: {
    modelValue: {
      type: String as PropType<string>,
      default: ''
    },
    fetch: {
      type: Function as PropType<(val: string, cb: (val: FetchList[]) => void) => void>,
      required: true
    },
    searchKey: {
      type: String as PropType<string>,
      default: 'value'
    }
  },
  emits: ['select', 'focus', 'blur', 'update:modelValue', 'change'],
  setup (props: AutocompleteProps, ctx: SetupContext) {
    const currentValue = ref<string>(props.modelValue)
    useModelWatch(() => props.modelValue, currentValue)
    // 当前选中
    const currentIndex = ref<number>(-1)
    const itemDom = ref<HTMLElement>()
    const autocompleteDom = ref<HTMLElement>()
    const autocompleteListDom = ref<HTMLElement>()
    const autocompleteListShow = ref<boolean>(false)
    const isTopDropdown = ref<boolean>(false)
    useClickBlank(autocompleteDom, autocompleteListShow)
    // 列表
    const fetchList = computed(() => {
      if (!props.fetch || typeof props.fetch !== 'function') return []
      let list: FetchList[] = []
      props.fetch(currentValue.value, (val: FetchList[]) => {
        list = val
      })
      return list
    })
    // 定位
    const handlePosition = (): void => {
      if (!autocompleteDom.value || !autocompleteListDom.value) return
      const rect = autocompleteDom.value.getBoundingClientRect()
      const { height: contentHeight } = autocompleteListDom.value.getBoundingClientRect()
      const { clientHeight } = document.body
      autocompleteListDom.value.style.zIndex = String(getZIndex())
      autocompleteListDom.value.style.width = `${rect.width}px`
      autocompleteListDom.value.style.left = `${rect.left}px`
      autocompleteListDom.value.style.top = `${rect.top + rect.height}px`
      if ((rect.top + rect.height + contentHeight + 20) >= clientHeight) {
        isTopDropdown.value = true
        autocompleteListDom.value.style.top = `${rect.top - contentHeight - 5}px`
      } else {
        isTopDropdown.value = false
      }
    }

    const autocompleteFocus = (): void => {
      ctx.emit('focus', currentValue.value)
      autocompleteListShow.value = true
      currentIndex.value = -1
      setTimeout(handlePosition)
    }

    const autocompleteBlur = (): void => {
      ctx.emit('blur', currentValue.value)
    }

    const autocompleteClick = (val: FetchList): void => {
      if (!val[props.searchKey] || typeof val[props.searchKey] !== 'string') return
      currentValue.value = val[props.searchKey] as string
      currentIndex.value = -1
      autocompleteListShow.value = false
      ctx.emit('select', val)
    }

    const autocompleteKeydown = (ev: KeyboardEvent): void => {
      if (ev.code === 'ArrowUp') {
        currentIndex.value--
        if (currentIndex.value <= 0) currentIndex.value = 0
        if (autocompleteListDom.value && autocompleteListDom.value?.children[0]) {
          autocompleteListDom.value.children[0].scrollTo(0, 34 * currentIndex.value)
        }
      } else if (ev.code === 'ArrowDown') {
        currentIndex.value++
        if (currentIndex.value >= fetchList.value.length - 1) currentIndex.value = fetchList.value.length - 1
        if (autocompleteListDom.value && autocompleteListDom.value?.children[0]) {
          autocompleteListDom.value.children[0].scrollTo(0, 34 * currentIndex.value)
        }
      } else if (ev.code === 'Enter') {
        if (fetchList.value[currentIndex.value]) autocompleteClick(fetchList.value[currentIndex.value])
      }
    }

    onMounted(() => {
      document.addEventListener('scroll', handlePosition, true)
    })
    onBeforeUnmount(() => {
      document.removeEventListener('scroll', handlePosition, true)
    })

    return {
      currentValue,
      fetchList,
      isTopDropdown,
      itemDom,
      autocompleteDom,
      autocompleteListDom,
      autocompleteListShow,
      currentIndex,
      autocompleteKeydown,
      autocompleteFocus,
      autocompleteBlur,
      autocompleteClick
    }
  }
})
</script>
